package huajiu.com.base;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;

/**
 * 文件操作工具 1．文件的分割，原理是用输入流去读取文件，将读取规定大小的流再输出支指定的文件，直到整个把整个文件读取结束．
 * 2．文件合并，文件的合并原理与分割正好想反，就是把所有的子文件按序号都读取到一个输入流中，然后再把输入流中的东西全部输出到
 * 同一个文件输出流中，这样就可以把分割的文件合并到一个文件中去了．
 * 
 */
public class FileUtil {

	private static Logger logger = Logger.getLogger(FileUtil.class);

	/**
	 * 可以设置文件在读取时一次读取文件的大小
	 */
	private static int length = 1024;

	/**
	 * 单位：KB
	 */
	public static final Long KB = 1 * 1024L;

	/**
	 * 单位：MB
	 */
	public static final Long MB = 1024 * KB;

	/**
	 * 单位：GB
	 */
	public static final Long GB = 1024 * MB;

	/**
	 * 分割文件时每个文件的大小为500M
	 */
	public static final Long FILE_MAX_SIZE = 500 * MB;

	/**
	 * 切割文件后,子文件的保存路径
	 */
	public static final String STORE_PATH = "./split";

	/**
	 * 合并文件后,文件的保存路径
	 */
	public static final String STORE_UNION = "./union";

	/**
	 * 判断一个文件名是否是子文件的正则表达式,格式为: 日期_文件名_part子文件总数.子文件序号 如：
	 * 20140910164150_1.rar_part10.1
	 */
	public static final String PARTFILE_PATTERN = "[0-9]{14}_.+_part[0-9]+\\.[0-9]+$";

	public static SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmss");

	/**
	 * 使用系统默认的子文件的大小(500M)，对文件进行分割，并且分割后的文件保存在默认目录
	 * 
	 * @param filePath
	 *            切割文件的路径
	 * @return 子文件文件名列表
	 */
	public static List<String> fileSplit(String filePath) throws Exception {
		return fileSplit(filePath, STORE_PATH, FILE_MAX_SIZE);
	}

	/**
	 * 对文件进行分割，并且分割后的文件保存在默认目录
	 * 
	 * @param filePath
	 *            切割文件的路径
	 * @param size
	 *            子文件的大小
	 * @return 子文件文件名列表
	 */
	public static List<String> fileSplit(String filePath, Long size) throws Exception {
		return fileSplit(filePath, STORE_PATH, size);
	}

	/**
	 * 使用系统默认的子文件的大小(500M)，对文件进行分割，并且将分割后的文件保存在指定的目录
	 * 
	 * @param filePath
	 *            切割文件的路径
	 * @param storePath
	 *            子文件保存的目录
	 * @return 子文件文件名列表
	 */
	public static List<String> fileSplit(String filePath, String storePath) throws Exception {
		return fileSplit(filePath, storePath, FILE_MAX_SIZE);
	}

	/**
	 * 对文件进行分割
	 * 
	 * @param filePath
	 *            要切割的文件的路径
	 * @param storePath
	 *            切割后的文件的保存路径
	 * @param size
	 *            子文件的大小
	 * @return 子文件文件名列表
	 */
	public static List<String> fileSplit(String filePath, String storePath, Long size) throws Exception {
		if (filePath == null)
			throw new Exception("要分割的源文件不能为空...");

		File file = new File(filePath);

		File storeDir = new File(storePath);

		if (!file.exists())
			throw new Exception("要分割的源文件不存在...");

		if (!storeDir.exists())
			storeDir.mkdir();

		// 获取要分割成多少个子文件
		long num = file.length() % size == 0 ? file.length() / size : file.length() / size + 1;

		// 分割后的子文件列表
		List<String> partFiles = getPartFiles(file.getName(), (int) num);

		FileInputStream reader = new FileInputStream(file);

		// 子文件的开始和结束索引
		long beginIndex = 0;
		long endIndex = 0;

		int readcount = 0;

		// 存放读取到的字节的缓冲数组
		byte buffer[] = new byte[length];

		logger.info("开始分割文件,子文件数量" + num);

		for (int i = 0; i < num; i++) {
			logger.info("开始分割子文件：" + partFiles.get(i));

			FileOutputStream writer = new FileOutputStream(storePath + File.separator + partFiles.get(i));

			endIndex = (endIndex + size) > file.length() ? file.length() : endIndex + size;

			while (beginIndex < endIndex) {
				readcount = reader.read(buffer);
				beginIndex += readcount;
				writer.write(buffer, 0, readcount);
			}
			writer.close();

		}
		reader.close();
		return partFiles;
	}

	/**
	 * 获取被分割文件的子文件列表
	 * 
	 * @param filename
	 *            被分割文件的文件名(不包含路径)
	 * @param partcount
	 *            子文件的总数量
	 * @return 子文件文件名列表
	 */
	public static List<String> getPartFiles(String filename, Integer partcount) {

		List<String> partFiles = new ArrayList<String>();

		if (partcount <= 1) {
			partFiles.add(filename);
			return partFiles;
		}
		String partfilename = format.format(new Date()) + "_" + filename;

		for (int i = 1; i <= partcount; i++) {
			partFiles.add(getPartFileName(partfilename, i, partcount));
		}
		return partFiles;
	}

	/**
	 * 获取子文件的文件名
	 * 
	 * @param filename
	 *            被分割文件的文件名
	 * @param partnum
	 *            子文件的序号
	 * @param partcount
	 *            子文件的总数量
	 * @return 子文件的文件名
	 */
	public static String getPartFileName(String filename, Integer partnum, Integer partcount) {

		if (partcount == null || partcount == 1) {
			return filename;
		}
		return filename + "_part" + partcount + "." + partnum;

	}

	/**
	 * 判断一个列表中的子文件是否能够合并为一个文件
	 * 
	 * @param files
	 *            被分割文件的文件名
	 * @return 子文件的文件名
	 */
	public static boolean isCompleteFile(List<File> files) {
		if (files == null || files.size() == 0) {
			return false;
		}
		return files.size() == getPartFileCount(files.get(0).getName());
	}

	/**
	 * 根据一个子文件的文件名获取父文件被分割成的子文件的数量
	 * 
	 * @param filename
	 *            任意一个子文件的文件名
	 * @return 子文件的数量
	 */
	public static int getPartFileCount(String filename) {
		String count = filename.substring(filename.lastIndexOf("part") + 4, filename.lastIndexOf("."));
		return Integer.valueOf(count);

	}

	/**
	 * 获取一个目录下所有可以合并的文件
	 * 
	 * @param unionDir
	 * @return
	 */
	public static Map<String, List<File>> getUnionFiles(File unionDir) {

		File list[] = unionDir.listFiles();

		Map<String, List<File>> map = new HashMap<String, List<File>>();

		for (File file : list) {
			String name = file.getName();
			if (name.matches(PARTFILE_PATTERN)) {
				String unionFilename = name.substring(0, name.lastIndexOf("_"));// name.indexOf("_")+1
				if (map.containsKey(unionFilename)) {
					map.get(unionFilename).add(file);
				} else {
					List<File> parts = new ArrayList<File>();
					parts.add(file);
					map.put(unionFilename, parts);
				}
			}
		}

		FileComparator fileComparator = new FileComparator();

		List<String> delList = new ArrayList<String>();// 用来装子文件缺失的元素

		for (Entry<String, List<File>> entry : map.entrySet()) {

			if (isCompleteFile(entry.getValue())) {
				Collections.sort(entry.getValue(), fileComparator);
			} else {
				// System.out.println("文件缺失,不能合并");
				logger.info("文件缺失,不能合并");
				delList.add(entry.getKey());
			}
		}

		for (String key : delList) {
			map.remove(key);
		}
		return map;
	}

	/**
	 * 合并文件，合并后的文件保存在系统的默认目录
	 * 
	 * @param unionDir
	 *            要合并的文件所在的目录,系统会自动将该目录下所有的子文件合并
	 * @throws Exception
	 */
	public static void union(String unionDir) throws Exception {
		union(unionDir, STORE_UNION, true);
	}

	/**
	 * 合并文件
	 * 
	 * @param unionDir
	 *            要合并的文件所在的目录,系统会自动将该目录下所有的子文件合并
	 * @param storeDir
	 *            合并后的文件保存的目录
	 * @throws Exception
	 */
	public static void union(String unionDir, String storeDir) throws Exception {
		union(unionDir, storeDir, true);
	}

	/**
	 * 合并文件
	 * 
	 * @param unionDir
	 *            要合并的文件所在的目录,系统会自动将该目录下所有的子文件合并
	 * @param storeDir
	 *            合并后的文件保存的目录
	 * @param isDelete
	 *            合并完成后是否删除子文件
	 * @throws Exception
	 */
	public static void union(String unionDir, String storeDir, boolean isDelete) throws Exception {
		File unionDirs = new File(unionDir);

		File storeDirs = new File(storeDir);

		union(unionDirs, storeDirs, isDelete);
	}

	/**
	 * 合并文件
	 * 
	 * @param unionDir
	 *            要合并的文件所在的目录,系统会自动将该目录下所有的子文件合并
	 * @param storeDir
	 *            合并后的文件保存的目录
	 * @param isDelete
	 *            合并完成后是否删除子文件
	 * @throws Exception
	 */
	@SuppressWarnings("unused")
	public static void union(File unionDirs, File storeDirs, boolean isDelete) throws Exception {

		if (!unionDirs.exists()) {
			return;
		}

		if (!storeDirs.exists()) {
			storeDirs.mkdir();
		}

		Map<String, List<File>> map = getUnionFiles(unionDirs);

		byte buffer[] = new byte[1024];

		int readcount = 0;

		for (Entry<String, List<File>> entry : map.entrySet()) {

			String storeFileName = storeDirs.getAbsolutePath() + File.separator
					+ entry.getKey().substring(entry.getKey().indexOf("_") + 1);

			File storeFile = new File(storeFileName);

			for (int i = 2; storeFile.exists(); i++) {

				int index = storeFileName.lastIndexOf(".");

				if (index > -1) {
					storeFile = new File(storeFileName.substring(0, index) + i + storeFileName.substring(index));
				} else {
					storeFile = new File(storeFileName + i);
				}
			}

			FileOutputStream writer = new FileOutputStream(storeFile);

			for (File f : entry.getValue()) {
				FileInputStream reader = new FileInputStream(f);
				while ((readcount = reader.read(buffer)) != -1) {
					writer.write(buffer);
				}
				reader.close();
			}
			writer.close();

			if (isDelete) {
				delete(entry.getValue());
			}
		}
	}

	/**
	 * 将文件读取到一个输出流中
	 * 
	 * @param path
	 *            文件的路径
	 * @return 返回一个输出流
	 * @throws Exception
	 */
	public static ByteArrayOutputStream read(String path) throws Exception {
		try {
			FileInputStream in = new FileInputStream(path);
			return read(in);
		} catch (FileNotFoundException e) {
			logger.error("文件不存在");
			throw new Exception("文件不存在", e);
		} catch (IOException e) {
			logger.error("读取文件失败");
			throw new Exception("读取文件失败", e);
		}
	}

	/**
	 * 将一个输入流的数据读取到一个输出流中
	 * 
	 * @param in
	 *            输入流
	 * @return 返回一个输出流
	 * @throws Exception
	 */
	public static ByteArrayOutputStream read(InputStream in) throws Exception {
		try {
			int len = 0;
			byte[] buffer = new byte[length];
			ByteArrayOutputStream out = new ByteArrayOutputStream();

			while ((len = in.read(buffer)) > 0) {
				out.write(buffer, 0, len);
			}
			in.close();
			out.close();
			return out;
		} catch (IOException e) {
			logger.error("读取文件失败");
			throw new Exception("读取文件失败", e);
		}
	}

	/**
	 * 读取文件
	 * 
	 * @param path
	 *            文件的路径
	 * @return 返回一个输入流
	 * @throws Exception
	 */
	public static InputStream readAsStream(String path) throws Exception {
		try {
			FileInputStream in = new FileInputStream(path);
			return in;
		} catch (FileNotFoundException e) {
			logger.error("文件不存在");
			throw new Exception("文件不存在", e);
		}
	}

	/**
	 * 读取文件,并将读取到的数据作为字符串返回
	 * 
	 * @param path
	 *            文件的路径
	 * @return 读取到的内容
	 * @throws Exception
	 */
	public static String readAsString(String path) throws Exception {
		return read(path).toString();
	}

	/**
	 * 读取文件,并将读取到的数据作为字节数组返回
	 * 
	 * @param path
	 * @return 读取到的内容
	 * @throws Exception
	 */
	public static byte[] readAsBytes(String path) throws Exception {
		return read(path).toByteArray();
	}
	/**
	 * 读取文件,并将读取到的数据作为字节数组返回
	 * 
	 * @param path
	 * @return 读取到的内容
	 * @throws Exception
	 */
	public static byte[] readAsBytes(InputStream in) throws Exception {
		return read(in).toByteArray();
	}
     
	/**
	 * 将输入流中的数据写入到输出流中
	 * 
	 * @param out
	 * @param in
	 * @throws Exception
	 * @throws IOException
	 */
	public static void write(OutputStream out, InputStream in) throws IOException, Exception {
		out.write(read(in).toByteArray());
		out.close();
	}

	/**
	 * 将数据写入流中
	 * 
	 * @param out
	 *            输出流
	 * @param content
	 *            内容
	 * @throws Exception
	 * @throws IOException
	 */
	public static void write(OutputStream out, String content) throws IOException, Exception {
		out.write(content.getBytes());
		out.close();
	}

	/**
	 * 将数据写入流中
	 * 
	 * @param out
	 *            输出流
	 * @param content
	 *            内容
	 * @throws Exception
	 * @throws IOException
	 */
	public static void write(OutputStream out, byte[] content) throws IOException, Exception {
		out.write(content);
		out.close();
	}

	/**
	 * 将输入流中的数据写入到文件中
	 * 
	 * @param path
	 *            文件路径
	 * @param in
	 *            输入流
	 * @throws Exception
	 * @throws IOException
	 */
	public static void write(String path, InputStream in) throws IOException, Exception {
		write(path, read(in).toByteArray(), false);
	}

	/**
	 * 将输入流中的数据写入到文件中
	 * 
	 * @param path
	 *            文件路径
	 * @param content
	 *            内容
	 * @throws Exception
	 * @throws IOException
	 */
	public static void write(String path, String content) throws IOException, Exception {
		write(path, content.getBytes(), false);
	}

	/**
	 * 将输入流中的数据写入到文件中
	 * 
	 * @param path
	 *            文件路径
	 * @param in
	 *            输入流
	 * @param append
	 *            写入时是否将内容追加到文件末尾
	 * @throws Exception
	 * @throws IOException
	 */
	public static void write(String path, InputStream in, boolean append) throws IOException, Exception {
		write(path, read(in).toByteArray(), append);
	}

	/**
	 * 将内容写入流中
	 * 
	 * @param out
	 *            输出流
	 * @param content
	 *            内容
	 * @param append
	 *            写入时是否将内容追加到文件末尾
	 * @throws Exception
	 * @throws IOException
	 */
	public static void write(String path, String content, boolean append) throws IOException, Exception {
		write(path, content.getBytes(), append);
	}

	/**
	 * 将内容写入流中
	 * 
	 * @param out
	 *            输出流
	 * @param content
	 *            内容
	 * @param append
	 *            写入时是否将内容追加到文件末尾
	 * @throws Exception
	 * @throws IOException
	 */
	public static void write(String path, byte[] content, boolean append) throws IOException, Exception {
		FileOutputStream out = new FileOutputStream(path, append);
		out.write(content);
		out.close();
	}

	/**
	 * 创建文件
	 * 
	 * @param path
	 * @throws Exception
	 */
	public static File create(String path) throws Exception {
		File file = new File(path);
		if (!file.exists()) {
			try {
				if (file.isFile()) {
					file.createNewFile();
				} else {
					file.mkdir();
				}
			} catch (IOException e) {
				logger.error("创建文件失败");
				throw new Exception("创建文件失败", e);
			}
		}
		return file;
	}

	/**
	 * 判断文件是否存在,存在返回true,不存在返回false
	 * 
	 * @param path
	 *            文件路径
	 * @throws Exception
	 */
	public static boolean exist(String path) throws Exception {
		return exist(path, false);
	}

	/**
	 * 判断文件是否存在,存在返回true,不存在返回false,当不存在时根据autoCreate参数判断是否自动创建文件
	 * 
	 * @param path
	 *            文件路径
	 * @param autoCreate
	 *            文件不存在时是否自动创建文件,true表示自动创建文件
	 * @throws Exception
	 */
	public static boolean exist(String path, boolean autoCreate) throws Exception {
		File file = new File(path);

		if (!file.exists()) {
			if (autoCreate) {
				try {
					if (file.isFile()) {
						file.createNewFile();
					} else {
						file.mkdir();
					}
				} catch (IOException e) {
					logger.error("创建文件失败");
					throw new Exception("创建文件失败", e);
				}
			}
			return false;
		}
		return true;
	}

	/**
	 * 删除文件
	 * 
	 * @param files
	 *            要删除的文件列表
	 */
	public static void delete(List<File> files) {
		if (files == null || files.size() == 0) {
			return;
		}
		for (File file : files) {
			file.delete();
		}
	}

	/**
	 * 删除文件
	 * 
	 * @param files
	 *            要删除的文件数组
	 */
	public static void delete(File... files) {
		if (files == null || files.length == 0) {
			return;
		}
		for (File file : files) {
			file.delete();
		}
	}

	/**
	 * 文件排序器,对子文件进行排序
	 * 
	 * @author sucan
	 *
	 */
	static class FileComparator implements Comparator<File> {

		public int compare(File file1, File file2) {

			String filename1 = file1.getName();

			String filename2 = file1.getName();

			int partnum1 = Integer.valueOf(filename1.substring(filename1.lastIndexOf(".") + 1));
			int partnum2 = Integer.valueOf(filename2.substring(filename2.lastIndexOf(".") + 1));

			return partnum1 - partnum2;
		}
	}

	/**
	 * 获取文件的后缀名
	 * 
	 * @param fileName
	 *            : 文件名
	 * @return 文件的后缀名,如: jpg、png、html
	 */
	public static String getExt(String fileName) {
		if (fileName == null || "".equals(fileName.trim()) || fileName.indexOf(".") == -1)
			return "";
		return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
	}

}
